/*

gcc pfind.c -lpthread && ./a.out ../ main

*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>

#define WORKER_NUMBER 6
#define TASK_NUMBER 16
typedef struct task {
    int is_end;
    char path[128];
    char string[128];
}task;
task tasks[TASK_NUMBER];
int head, tail;

typedef struct sema{
    int value;
    pthread_mutex_t mutex;
    pthread_cond_t cond;
}sema;
sema mutex, prod, cons;

void find_file(char *path, char *target)
{
    FILE *file = fopen(path, "r");
    char line[256];
    while (fgets(line, sizeof(line), file)) {
        if (strstr(line, target))
            printf("%s: %s", path, line);
    }
    fclose(file);
}

void sema_init(sema* s, int value)
{
    s->value = value;
    pthread_cond_init(&s->cond, NULL);
    pthread_mutex_init(&s->mutex, NULL);
}

void P(sema* s)
{
    pthread_mutex_lock(&s->mutex);
    while (s->value <= 0)
        pthread_cond_wait(&s->cond, &s->mutex);

    s->value--;
    pthread_mutex_unlock(&s->mutex);
}

void V(sema* s)
{
    pthread_mutex_lock(&s->mutex);
    pthread_cond_signal(&s->cond);
    s->value++;
    pthread_mutex_unlock(&s->mutex);
}

void produce(int is_end, char *path, char *target)
{
    P(&prod);
    P(&mutex);

    // printf("produce is_end: %d\n", is_end);
    // printf("produce path: %s\n", path);

    tasks[head].is_end = is_end;
    strcpy(tasks[head].path, path);
    strcpy(tasks[head].string, target);
    head++;
    head %= TASK_NUMBER;

    V(&mutex);
    V(&cons);
}

void* consume(void* arg)
{
    while (1) 
    {
        P(&cons);
        P(&mutex);  

        // 从任务队列中获取一个任务 task;
        task t = tasks[tail++];
        tail %= TASK_NUMBER;

        // printf("    consumer is_end: %d\n", t.is_end);

        if (t.is_end)
        {
            // printf("consumer end!!!\n");
            V(&mutex);
            V(&prod);
            break;
        }
        else
            find_file(t.path, t.string);

        V(&mutex);
        V(&prod);
    }

    return NULL;
}

void find_dir(char *path, char *target)
{
    DIR *dir = opendir(path);
    struct dirent *entry;
    while (entry = readdir(dir)) {
        if (strcmp(entry->d_name, ".") == 0)
            continue;
        if (strcmp(entry->d_name, "..") == 0)
            continue;
        if (entry->d_type == DT_DIR)
        {
            char curPath[256];
            strcpy(curPath, path);
            strcat(curPath, "/");
            strcat(curPath, entry->d_name);
            find_dir(curPath, target);
        }
        if (entry->d_type == DT_REG)
        {
            char curPath[256];
            strcpy(curPath, path);
            strcat(curPath, "/");
            strcat(curPath, entry->d_name);
            // find_file(curPath, target);
            produce(0, curPath, target);
        }
    }
    closedir(dir);
}

int main(int argc, char **argv)
{
    if (argc != 3) {
        puts("Usage: sfind file string");
        return 0;
    }
    char *path = argv[1];
    char *string = argv[2];
    struct stat info;
    stat(path, &info);

    if (!S_ISDIR(info.st_mode))
        find_file(path, string);
    else
    {
        head = tail = 0;
        sema_init(&mutex, 1);
        sema_init(&prod, TASK_NUMBER);
        sema_init(&cons, 0);

        pthread_t pid[WORKER_NUMBER];
        for (int i = 0; i < WORKER_NUMBER; i++)
            pthread_create(pid+i, NULL, consume, NULL);

        find_dir(path, string);

        for (int i = 1; i <= WORKER_NUMBER; i++)
            produce(1, "\0", "\0");

        for (int i = 0; i < WORKER_NUMBER; i++)
            pthread_join(pid[i], NULL);
    }
}